package ift585.client;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;


public class Client {

	private static final int INIT_TIMEOUT = 10000;
	private static final int PACKET_LENGTH = 1024;
	private String serverIP;
	private int serverPort;
	
	private DatagramSocket serverInitComm = null;
	private InetAddress channelAddress;
	private int channelPort;
	private String fileToSend;
	
	private static int FAILED_TRANSACTION_THRESHOLD = 5;
	
	public Client(String serverAddress, int serverInitPort, String fileName) throws SocketException {
		serverIP = serverAddress;
		serverPort = serverInitPort;
		serverInitComm = new DatagramSocket();
		fileToSend = fileName;
	}
	
	public void execute(){
		try {
			System.out.println("Initialize communication");
			initComunication();
			System.out.println("Request accepted, beginning transfert");
			senFile();
		}
		catch (SocketTimeoutException e) {
			System.out.println("Server is not responding to initial request. Server may be too busy.");
//			e.printStackTrace();
		}
		catch (UnknownHostException e) {
			System.out.println("Specified host is unreachable. Try to validate host address.");
//			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("Cannot create datagram socket.");
//			e.printStackTrace();
		}
		finally{
			System.out.println("Application will terminate.");
		}
	}

	private void initComunication() throws UnknownHostException, IOException {
		
		byte [] sendBuffer = new byte[PACKET_LENGTH];
		sendBuffer = new String(fileToSend).getBytes();
		
		
		serverInitComm.send(new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName(serverIP), serverPort));
		
		byte [] receiveBuffer = new byte[PACKET_LENGTH];
		DatagramPacket receivedPacket = new DatagramPacket(receiveBuffer, PACKET_LENGTH);
		
		serverInitComm.setSoTimeout(INIT_TIMEOUT);
		serverInitComm.receive(receivedPacket);
		serverInitComm.setSoTimeout(0);
		
		channelAddress = receivedPacket.getAddress();
		channelPort = receivedPacket.getPort();
	}

	
	private void senFile() throws IOException {
		FenetreClient clientWindow = new FenetreClient(fileToSend);
		TransactionHandler transacHandler = new TransactionHandler();
		List<DatagramPacket> toSend = new ArrayList<DatagramPacket>(); 
		int failedTransaction = 0;
		boolean keepSending = true;
		
		List<byte[]> dataToSend = clientWindow.getProchainsPaquets();
		byte[] response = new byte[1];
		response[0] = 0;
		
		while(dataToSend.size() != 0 && keepSending){
			
			toSend = buildPacketToSend(dataToSend);	
			
			try {
				
				response = transacHandler.doTransaction(toSend, serverInitComm);
				failedTransaction = 0;
			} catch (UnsuccessfulTransactionException e) {
				++failedTransaction;
				System.out.println("Failed transaction detected");
				if(failedTransaction >= FAILED_TRANSACTION_THRESHOLD){
					keepSending = false;
					System.out.println("Too much failed transaction. Aborting transmission");
				}
			}
			
			dataToSend = clientWindow.getProchainsPaquets(response);
			toSend.clear();
		}
		
		sendEndOfTransmission();
	}

	private List<DatagramPacket> buildPacketToSend(List<byte[]> dataToSend) {
		
		List<DatagramPacket> toSend = new ArrayList<DatagramPacket>();
		
		for(int i = 0 ; i < dataToSend.size() ; ++i){
			toSend.add(new DatagramPacket(dataToSend.get(i), dataToSend.get(i).length, channelAddress, channelPort));
		}
		
		return toSend;
	}
	
	private void sendEndOfTransmission() throws IOException {
		System.out.println("Sending end of transmission");
		byte [] endOfTransmission = new byte [1];
		endOfTransmission[0] = FenetreClient.CLOSE_ACK;
		serverInitComm.send(new DatagramPacket(endOfTransmission, endOfTransmission.length, channelAddress, channelPort));
	}
	
	public static void main(String[] args) {
		try {
			System.out.println("Client initialization");
			Client oneClient = new Client(args[0], Integer.valueOf(args[1]), args[2]);
			oneClient.execute();
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

}
